package tasks

import java.io.BufferedReader
import java.io.FileReader
import java.util.regex.Pattern
import java.io.PrintWriter
import java.io.File
import java.util.regex.Matcher
import java.util.Properties
import java.io.InputStream
import java.io.FileInputStream

case class EPModule(name: String, jsfilename: String, services: Seq[EPService]) {
  def toJavascript = s"""define([ 'ng', 'ngResource' ], function(angular) {
  var app=angular.module("${name}Module", [ "ngResource" ]);
  app.factory("${name}",function($$resource) {
	var service = {};
  	${(services.map(_.toJavascript).mkString("\n"))}
	return service;
  });
});"""
  def toSTJS =
    s"""
	|	public static interface ${name}EP {
			${services.map(_.toSTJS).mkString("\n")}
	|	}
	""".stripMargin

  def toAndroid =
    s"""
	|	public static class ${name}EP{
	|${services.map(_.toAndroid).mkString("\n")}
	|	}
	""".stripMargin
}

case class EPService(method: String, url: String, name: String, params: Seq[(String, String)], responseType: String, bodyType: String) {

  def urlParamsSignature = params.map((t) => s"${t._2} ${t._1}").mkString(",")

  def commaAfterParams = if (urlParamsSignature.length() > 0) "," else ""
  def commaAfterBody = if (urlParamsSignature.length() > 0 || withBody) "," else ""

  def response = { if (responseType.equals("void")) "EmptyPromise" else responseType }

  def callbackSignature = s"JsProcedure1 <$response> callback"
  def errorSignature = s"JsProcedure1<ErrorObject> error"

  def withBody = !method.equals("GET") && !bodyType.equals("void")

  def basicSignature = s"public $response $name ($urlParamsSignature${if (withBody) s"$commaAfterParams $bodyType body" else ""}"

  def javadoc(withCallback: Boolean) = s"/** ${if (withCallback) "Con" else "Sin"} callback */"

  def androidURL = {
    var aUrl = url;
    params.foreach { t =>
      val p = t._1
      //println(s"Buscando :$p en $aUrl")
      aUrl = aUrl.replace(s":$p", s"""" + $p + """")
    }
    aUrl
  }

  def isArray = responseType.startsWith("Array")

  def handlerResponseType = responseType.replace("Array<", "").replace(">", "").replace("void", "Void")

  def toSTJS = s"""
		  ${javadoc(false)}
    	$basicSignature);
    	${javadoc(true)}
    	$basicSignature$commaAfterBody $callbackSignature);
    	/** Incluye callback de error */
    	$basicSignature$commaAfterBody $callbackSignature, $errorSignature);
    """
  def toAndroid = s"""|		public static void $name($urlParamsSignature${if (withBody) s"$commaAfterParams $bodyType body" else ""}$commaAfterBody EPCallback${if (isArray) s"<List<$handlerResponseType>>" else s"<$handlerResponseType>"} handler) {
  	|			EPClient.addRequest(new J${if(isArray)"List" else "Object"}Request<$handlerResponseType>(Method.$method,"$androidURL", ${if(withBody)"body"else"null"}, $handlerResponseType.class, handler));
  	|		}""".stripMargin

  def toJavascript = {
    val paramsWithoutType = params.map(_._1)
    val bodyParam = if (withBody) " body," else ""
    val bodyParam2 = if (withBody) " body," else if (!method.equals("GET")) " {}," else ""
    val comma = if (paramsWithoutType.length > 0) "," else ""
    s"""
		  service.${name}Resource = $$resource('$url',{},{$name:{method:'$method',isArray:$isArray}}).$name;
		  service.$name = function(${paramsWithoutType.mkString(",")}$comma$bodyParam callback, error ) { return service.${name}Resource({${paramsWithoutType.map(p => s"$p:$p").mkString(",")}},$bodyParam2 callback, error ); }
     """
  }
}

object GenerateEndPoints {

  def toJavascript(modules: List[EPModule]) {
    modules.foreach { module =>
      val jspw = new PrintWriter(module.jsfilename + ".js");
      jspw.println(module.toJavascript)
      jspw.close()
      println("creado servicio " + module.name)
    }
  }

  def toSTJS(modules: List[EPModule]) {
    val file = new File("client-app/client/common/EndPoints.java")
    if (!file.exists())
      file.createNewFile();
    val pw = new PrintWriter(file);
    pw.println("package client.common;")
    pw.println("import org.stjs.javascript.annotation.STJSBridge;")
    pw.println("import org.stjs.javascript.Array;")
    pw.println("import client.common.javascript.*;")
    pw.println("import client.common.angular.*;")
    pw.println("import client.common.Models.*;")
    pw.println("import vos.*;")
    pw.println()
    pw.println("@STJSBridge")
    pw.println("public interface EndPoints{")
    modules.foreach { module =>
      pw.println(module.toSTJS)
    }
    pw.println("}")
    pw.close();
  }

  def toAndroid(modules: List[EPModule]) {
    val p = new Properties();
    val is = new FileInputStream("dev-source/tasks.properties")
    p.load(is)
    val wsFolder = p.getProperty("android-ws")
    val projectFolder = p.getProperty("android-project")
    val rootPackage = p.getProperty("android-root-package") 
    val servicesPackage = rootPackage+ ".services"
    val endpointsRoute = s"$wsFolder/$projectFolder/src/${servicesPackage.replace(".", "/")}/EndPoints.java" 
    println(s"Generando a: $endpointsRoute")
    val endpointsFileObject = new File(endpointsRoute)
    is.close()

    if (!endpointsFileObject.exists())
      endpointsFileObject.createNewFile();
    val endpointsFile = new PrintWriter(endpointsFileObject);

    endpointsFile.println(s"""package $servicesPackage;
    |import java.util.List;
    |import com.android.volley.Request.Method;
    |import $rootPackage.models.Models.*;
    |import $rootPackage.vos.*;
    |import $rootPackage.util.EPCallback;
    |import $rootPackage.util.JListRequest;
    |import $rootPackage.util.JObjectRequest;

        
    |public class EndPoints {
    |${modules.map({ m => println("Creado android service " + m.name); m.toAndroid }).mkString("\n")}
    |}
    """.stripMargin)
    endpointsFile.close();
  }

  def apply {
    val br = new BufferedReader(new FileReader("conf/routes"));

    var line = nextLine(br);
    var modules = List[EPModule]();
    while (line match {
      case Some(_) => true
      case None    => false
    }) {
      val lline = line.get;
      if (lline.startsWith("#MODULE")) {
        modules = createModule(lline.split("\\s+"), br) :: modules;
      }
      line = nextLine(br);
    }

    br.close();

    toJavascript(modules)
    toSTJS(modules)
    toAndroid(modules)

  }

  private def createModule(desc: Array[String], br: BufferedReader): EPModule = {

    var maybeLine = nextLine(br);

    var services = List[EPService]();

    while (maybeLine.map(!_.startsWith("#ENDMODULE")).getOrElse(false)) {
      val line = maybeLine.get;
      if (line.startsWith("#SERVICE")) {
        val serv = createService(line.replaceAll("#SERVICE\\s*", "").trim(), br)

        serv.foreach { serv =>
          services = serv :: services
        }

      }
      maybeLine = nextLine(br);
    }

    EPModule(desc(1), desc(2), services)
  }

  private def createService(desc: String, br: BufferedReader) = {

    // group 4 tiene los parametros

    nextLine(br).map(s => pattern.matcher(s))
      .filter((mat: Matcher) => mat.matches()).map { m =>

        val types = desc.split("\\s+");
        val url = m.group(2)
        val method = m.group(1);
        val name = m.group(3);
        val params: Seq[(String, String)] = m.group(4).split(",").map(_.trim()).filter(_.length() > 0).map((p) => {
          val pt = p.split(":")
          (pt(0), pt(1))
        })
        EPService(method, url, name, params, types(0), types(1))

      }

  }

  val pattern = Pattern.compile("^(\\w+)\\s+([\\w/:]+)\\s+[\\w\\.]+?(\\w+?)\\((.*?)\\)$");
  def main(args: Array[String]) {
    GenerateEndPoints.apply
  }

  def nextLine(br: BufferedReader): Option[String] = {
    try {
      var line = br.readLine().trim();
      while (line.length() == 0)
        line = br.readLine().trim();

      Some(line)

    } catch {
      case _: Throwable => None
    }
  }

}